#pragma once
#include <string>
#include <type_traits>
#include <algorithm>
#include <cstring>

enum class Endian: char{
	BIGENDIAN,
	LITTLEENDIAN
};

class cbuff {
public:
	cbuff(Endian e = Endian::BIGENDIAN):endian(e) {}
	
	template<class T>
	cbuff& push(const T& t, int len = 0) {
		len = (len ? len : sizeof(t));
		auto addr = std::addressof(t);
		const char *p = reinterpret_cast<const char*>(addr);
		if (endian == Endian::LITTLEENDIAN) {
			buf_.append(p, len);
		} else {
			std::string s(p, len);
			std::reverse(s.begin(), s.end());
			buf_.append(s);
		}
		return *this;
	}

	template<class T>
	cbuff& push(const T *t, int len) { //for a pointer, must set len.
		const char *p = reinterpret_cast<const char*>(t);
		buf_.append(p, len);
		return *this;
	}

	template<class T>
	cbuff& pop(T &t, int len = 0) {
		len = (len ? len : sizeof(t));
		len = std::min(len, int(size()));
		char *p = reinterpret_cast<char*>(&t);
		if (endian == Endian::LITTLEENDIAN) {
			std::memcpy(p, &buf_[cur_], len);
		} else {
			std::string s(&buf_[cur_], len);
			std::reverse(s.begin(), s.end());
			std::memcpy(p, &s[0], len);
		}
		cur_ += len;
		return *this;
	}

	template<class T>
	cbuff& pop(T *t, int len) { //for a pointer, must set len.
		len = std::min(len, int(size()));
		char *p = reinterpret_cast<char*>(t);
		std::memcpy(p, &buf_[cur_], len);
		cur_ += len;
		return *this;
	}

	template<class T>
	cbuff& peek(T &t, int len = 0) {
		len = (len ? len : sizeof(t));
		len = std::min(len, int(size()));
		char *p = reinterpret_cast<char*>(&t);
		if (endian == Endian::LITTLEENDIAN) {
			std::memcpy(p, &buf_[cur_], len);
		} else {
			std::string s(&buf_[cur_], len);
			std::reverse(s.begin(), s.end());
			std::memcpy(p, &s[0], len);
		}
		return *this;
	}

	size_t size() {
		if (buf_.length() < cur_)
			return 0;
		return buf_.length() - cur_;
	}

	size_t seek(size_t pos) {
		pos = std::min(size(), pos);
		cur_ += pos;
		return cur_;
	}

	const char* serialize() {
		return &buf_[cur_];
	}
private:
	std::string buf_;
	size_t cur_ = 0;
	Endian endian;
};